home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 7: Sunsite / Linux Cubed Series 7 - Sunsite Vol 1.iso / system / news / readers / skim-0.8 / skim-0 / skim-0.8.4 / AutoSelectAndKill.c < prev    next >
C/C++ Source or Header  |  1996-02-18  |  16KB  |  656 lines

  1. /*
  2.  * NAME
  3.  *   AutoSelectAndKill.c
  4.  * USAGE
  5.  *   AutoSelectAndKill NewsGroup
  6.  * DESCRIPTION
  7.  *   Functions to automatically select and kill subjects and authors.
  8.  * COPYRIGHT
  9.  *   Skim - Off-line news reading package optimized for slow lines.
  10.  *   Copyright (C) 1996  Rene W.J. Pijlman
  11.  *
  12.  *   This program is free software; you can redistribute it and/or modify
  13.  *   it under the terms of the GNU General Public License as published by
  14.  *   the Free Software Foundation; either version 2 of the License, or
  15.  *   (at your option) any later version.
  16.  *
  17.  *   This program is distributed in the hope that it will be useful,
  18.  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  19.  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  20.  *   GNU General Public License for more details.
  21.  *
  22.  *   You should have received a copy of the GNU General Public License
  23.  *   along with this program; if not, write to the Free Software
  24.  *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  25.  * VERSION
  26.  *   Skim version 0.8.4.
  27.  */
  28.  
  29. #include <stdio.h>
  30. #include <ctype.h>
  31. #include <assert.h>
  32. #include <regex.h>
  33. #include <string.h>
  34. #include <stdlib.h>
  35. #include <unistd.h>
  36.  
  37. #include "AutoSelectAndKill.h"
  38. #include "Skim.h"
  39. #include "SkimUtils.h"
  40. #include "MemAlloc.h"
  41. #include "VarBuf.h"
  42. #include "ArticleNumber.h"
  43. #include "StandardIO.h"
  44.  
  45. FILE_ID("$Header: /home/rene/sys/CVS_MasterSourceRepository/skim/AutoSelectAndKill.c,v 1.7 1996/02/17 22:47:29 rene Exp $");
  46.  
  47. #define USAGE "Usage: AutoSelectAndKill NewsGroup"
  48.  
  49. #define ARGC_NEWSGROUP        1
  50. #define NUMBER_OF_ARGUMENTS   2
  51.  
  52. #define REGEX_MAPSIZE  256
  53.  
  54.  
  55. static void DestroyPattern( regex_t * Pattern )
  56. {
  57.     if ( Pattern != NULL )
  58.     {
  59.     regfree( Pattern );
  60.  
  61.     MemFree( Pattern );
  62.     }
  63. }
  64.  
  65.  
  66. /* Like popen(3), but this implementation doesn't start a shell process. */
  67. static void OpenPipeToM4( StandardIO Pipe, const char * IncludeFileName )
  68. {
  69.     int fd[2];
  70.     int pid;
  71.     VarBuf PseudoFileName = VBCreate();
  72.  
  73.     VBPrintf( PseudoFileName, "m4 %s", IncludeFileName );
  74.  
  75.     if ( pipe(fd) < 0 )
  76.     {
  77.         perror("pipe");
  78.         exit( EXIT_FAILURE );
  79.     }
  80.  
  81.     pid = fork();
  82.     if ( pid > 0 )
  83.     {
  84.         /* Parent. */
  85.         close(fd[1]);
  86.         SIOFileOpenFileDescriptorVB(Pipe, fd[0], PseudoFileName, OpenModeRead);
  87.     }
  88.     else if ( pid == 0 )
  89.     {
  90.         /* Child. */
  91.         close(fd[0]);
  92.  
  93.         if ( dup2( fd[1], 1 ) < 0 )
  94.         {
  95.             perror("dup");
  96.             exit( EXIT_FAILURE );
  97.         }
  98.  
  99.         close(fd[1]);
  100.  
  101.         if ( execlp( "m4", "m4", IncludeFileName, NULL ) < 0 )
  102.         {
  103.             SIOPrintError( VBAsString( PseudoFileName ) );
  104.             exit( EXIT_FAILURE );
  105.         }
  106.     }
  107.     else
  108.     {
  109.         perror( "fork" );
  110.         exit( EXIT_FAILURE );
  111.     }
  112.  
  113.     VBDestroy(PseudoFileName);
  114. }
  115.  
  116.  
  117. /*
  118.  * SIDE EFFECTS
  119.  *     CompilePatternInFile changes the working directory to 
  120.  *     $SKIMDIR/PatternDirectory.
  121.  */
  122. static regex_t * CompilePatternInFile(
  123.     char * PatternDirectory,
  124.     char * FileName,
  125.     Boolean CaseSensitive )
  126. {
  127.     StandardIO PatternFile = SIOCreate();
  128.     regex_t * Pattern = NULL;
  129.     VarBuf M4Command = VBCreate();
  130.     VarBuf RegularExpression = VBCreate();
  131.     VarBuf SubExpression = VBCreate();
  132.     VarBuf CurrentWorkingDirectory = VBCreate();
  133.     int ErrorCode;
  134.  
  135.     Boolean FirstSubExpression = True;
  136.  
  137.     VBPrintf( CurrentWorkingDirectory, "%s/%s", SkimDirectory(), 
  138.               PatternDirectory );
  139.     if ( chdir( VBAsString( CurrentWorkingDirectory ) ) != 0 )
  140.     {
  141.     perror( "chdir" );
  142.     exit( EXIT_FAILURE );
  143.     }
  144.  
  145.     OpenPipeToM4( PatternFile, FileName );
  146.  
  147.     while ( VBReadLine( SubExpression, PatternFile, WITHOUT_NEWLINE ) )
  148.     {
  149.         /* Ignore empty lines and comment. */
  150.     if ( VBSize( SubExpression ) > 0 &&
  151.          *VBAsString( SubExpression ) != '#' )
  152.     {
  153.         if ( !FirstSubExpression )
  154.         {
  155.         VBAppendCharacter( RegularExpression, '|' );
  156.         }
  157.  
  158.         VBAppendVB( RegularExpression, SubExpression );
  159.  
  160.         FirstSubExpression = False;
  161.     }
  162.  
  163.     VBReset( SubExpression );
  164.     }
  165.  
  166.     SIOFileClose( PatternFile );
  167.  
  168.     WaitForAChild( VBAsString( M4Command ) );
  169.  
  170.     if ( VBSize(RegularExpression) > 0 )
  171.     {
  172.     int CompilationFlags = REG_EXTENDED | REG_NOSUB |
  173.                            (CaseSensitive ? 0 : REG_ICASE);
  174.  
  175.     Pattern = MemAlloc( sizeof(regex_t) );
  176.  
  177.     ErrorCode = regcomp( Pattern, VBAsString(RegularExpression),
  178.                          CompilationFlags );
  179.     if ( ErrorCode != 0 )
  180.     {
  181. #define     ERROR_BUFFER_SIZE 1024
  182.         char ErrorMessage[ERROR_BUFFER_SIZE];
  183.  
  184.         (void)regerror(ErrorCode, Pattern, ErrorMessage, ERROR_BUFFER_SIZE);
  185.  
  186.         SIOPrintf( StandardError,
  187.                  "Error compiling the %s pattern of group %s:\n%s\n",
  188.                      PatternDirectory, FileName, ErrorMessage );
  189.  
  190.         exit( EXIT_FAILURE );
  191.     }
  192.     }
  193.  
  194.     VBDestroy( CurrentWorkingDirectory );
  195.     VBDestroy( M4Command );
  196.     VBDestroy( SubExpression );
  197.     VBDestroy( RegularExpression );
  198.     SIODestroy(PatternFile);
  199.  
  200.     return Pattern;
  201. }
  202.  
  203. static void GetSubjectFromHeaderLine( 
  204.     VarBuf HeaderLine,
  205.     VarBuf Subject )
  206. {
  207.     if ( !AppendFieldFromFixedRecord( Subject, HeaderLine,
  208.                       OffsetOfSubject(), VARIABLE_LENGTH,
  209.                       VBRemoveTrailingBlanks ) )
  210.     {
  211.     SIOPrintf( StandardError, 
  212.                        "No subject field in header line.\n" );
  213.     exit( EXIT_FAILURE );
  214.     }
  215. }
  216.  
  217.  
  218. static void GetAuthorFromHeaderLine(
  219.     VarBuf HeaderLine,
  220.     VarBuf Author )
  221. {
  222.     if ( !AppendFieldFromFixedRecord( Author, HeaderLine, OffsetOfAuthor(),
  223.                       LengthOfAuthor(),
  224.                       VBRemoveTrailingBlanks  ) )
  225.     {
  226.     SIOPrintf( StandardError, "No author field in header line.\n" );
  227.     exit( EXIT_FAILURE );
  228.     }
  229. }
  230.  
  231.  
  232. static void GetArticleNumberFromHeaderLine( 
  233.     VarBuf HeaderLine, 
  234.     VarBuf ArticleNumber )
  235. {
  236.     if ( !AppendFieldFromFixedRecord( ArticleNumber, HeaderLine, 
  237.                                       OffsetOfArticleNumber(),
  238.                       LengthArticleNumber, 
  239.                       VBRemoveTrailingBlanks ) )
  240.     {
  241.     SIOPrintf( StandardError, 
  242.                        "No article number in header line.\n" );
  243.     exit( EXIT_FAILURE );
  244.     }
  245. }
  246.  
  247. static StandardIO KilledFile = NULL;
  248. static StandardIO SubjectsFile = NULL;
  249. VarBuf FullPathKilledFile = NULL;
  250. VarBuf FullPathSubjectsFile = NULL;
  251.  
  252. static VarBuf ArrayOfHeaderLines = NULL;
  253.  
  254. static void HeaderLineToSubjectsFile(
  255.     char * Newsgroup,
  256.     VarBuf HeaderLine,
  257.     Boolean AutoSelected )
  258. {
  259.     VarBuf NewHeaderLine = VBCreate();
  260.  
  261.     assert( ArrayOfHeaderLines != NULL );
  262.  
  263.     assert( VBSize(HeaderLine) > 0 && *VBAsString(HeaderLine) == NotSelected );
  264.  
  265.     VBPrintf( NewHeaderLine, "%c%s", AutoSelected ? Selected : NotSelected,
  266.           VBAsString(HeaderLine) + 1 );
  267.  
  268.     VBAppendVBReference( ArrayOfHeaderLines, NewHeaderLine );
  269.  
  270.     /* NewHeaderLine is destroyed in ASAKClose(). */
  271. }
  272.  
  273.  
  274. static void AutoSelectHeaderLine(
  275.     char * Newsgroup,
  276.     VarBuf HeaderLine )
  277. {
  278.     HeaderLineToSubjectsFile( Newsgroup, HeaderLine, True );
  279. }
  280.  
  281. static void NormalHeaderLine(
  282.     char * Newsgroup,
  283.     VarBuf HeaderLine )
  284. {
  285.     HeaderLineToSubjectsFile( Newsgroup, HeaderLine, False );
  286. }
  287.  
  288.  
  289. static void KillHeaderLine(
  290.     char * Newsgroup,
  291.     VarBuf HeaderLine )
  292. {
  293.     assert( Newsgroup != NULL && strlen(Newsgroup) > 0 );
  294.  
  295.     if ( KilledFile == NULL )
  296.     {
  297.         assert( FullPathKilledFile == NULL );
  298.  
  299.         KilledFile = SIOCreate();
  300.         FullPathKilledFile = VBCreate();
  301.  
  302.     VBPrintf( FullPathKilledFile, "%s/%s/%s",
  303.           SkimDirectory(), "Killed", Newsgroup );
  304.  
  305.     SIOFileOpenVB( KilledFile, FullPathKilledFile, OpenModeAppend );
  306.     }
  307.     assert( FullPathKilledFile != NULL );
  308.  
  309.     SIOPrintf( KilledFile, "%s\n", VBAsString(HeaderLine) );
  310. }
  311.  
  312. static Boolean Open = False;
  313. static VarBuf CurrentNewsgroup = NULL;
  314. static ArticleNumber HighestArticleNumber = 0;
  315.  
  316. static Boolean SubjectCaseSensitive;
  317. static Boolean AuthorCaseSensitive;
  318.  
  319. static regex_t * AutoSelectSubject = NULL;
  320. static regex_t * KillSubject = NULL;
  321. static regex_t * AutoSelectAuthor = NULL;
  322. static regex_t * KillAuthor = NULL;
  323.  
  324. void ASAKOpen( const char * Newsgroup )
  325. {
  326.     assert( !Open );
  327.     assert( Newsgroup != NULL && strlen(Newsgroup) > 0 );
  328.  
  329.     assert( CurrentNewsgroup == NULL );
  330.  
  331.     CurrentNewsgroup = VBCreate();
  332.     VBAppendString( CurrentNewsgroup, Newsgroup );
  333.  
  334.     assert (ArrayOfHeaderLines == NULL );
  335.     ArrayOfHeaderLines = VBCreate();
  336.  
  337.     HighestArticleNumber = 0;
  338.  
  339.     Open = True;
  340. }
  341.  
  342. #define RE_PREFIX "Re: "
  343. #define LEN_RE_PREFIX 4
  344.  
  345.  
  346. /*
  347.  * Compare two subjects (case insensitive), ignoring the optional "Re: " 
  348.  * prefix.
  349.  */
  350. static int PseudoThread( VarBuf Buf1, VarBuf Buf2 )
  351. {
  352.     char * p;
  353.     char * q;
  354.     int Compare;
  355.     Boolean pIsReply;
  356.  
  357.     if ( VBSize(Buf1) < OffsetOfSubject() )
  358.     {
  359.         SIOPrintf( StandardError, "Incorrect format: %V\n", Buf1 );
  360.         exit(EXIT_FAILURE);
  361.     }
  362.  
  363.     if ( VBSize(Buf2) < OffsetOfSubject() )
  364.     {
  365.         SIOPrintf( StandardError, "Incorrect format: %V\n", Buf2 );
  366.         exit(EXIT_FAILURE);
  367.     }
  368.  
  369.     p = VBAsString( Buf1 ) + OffsetOfSubject();
  370.     q = VBAsString( Buf2 ) + OffsetOfSubject();
  371.  
  372.     if ( !strncasecmp( p, RE_PREFIX, LEN_RE_PREFIX ) )
  373.     {
  374.         pIsReply = True;
  375.         p += LEN_RE_PREFIX;
  376.     }
  377.     else
  378.     {
  379.         pIsReply = False;
  380.     }
  381.  
  382.     if ( !strncasecmp( q, RE_PREFIX, LEN_RE_PREFIX ) )
  383.     {
  384.         q += LEN_RE_PREFIX;
  385.     }
  386.  
  387.     Compare = strcasecmp( p, q );
  388.  
  389.     /* A reply sorts after the subject it replies to. */
  390.     if ( Compare == 0 && pIsReply )
  391.     {
  392.     Compare = 1;
  393.     }
  394.  
  395.     return Compare;
  396. }
  397.  
  398.  
  399. /* Read the subjects from the Subjects file into memory for sorting. */
  400. static void ReadOldSubjects( 
  401.     VarBuf ArrayOfHeaderLines, 
  402.     VarBuf CurrentNewsgroup )
  403. {
  404.     StandardIO SubjectsFile = SIOCreate();
  405.     VarBuf FullPathSubjectsFile = VBCreate();
  406.  
  407.     assert( VBIsOK(ArrayOfHeaderLines) );
  408.     assert( VBIsOK(CurrentNewsgroup) && VBSize(CurrentNewsgroup) > 0 );
  409.  
  410.     VBPrintf( FullPathSubjectsFile, "%s/%s/%V",
  411.           SkimDirectory(), "Subjects", CurrentNewsgroup );
  412.  
  413.     SIOFileOpenVB( SubjectsFile, FullPathSubjectsFile, OpenModeReadIgnore );
  414.  
  415.     if ( SIOIsOpenForRead( SubjectsFile ) )
  416.     {
  417.         VarBuf HeaderLine = VBCreate();
  418.  
  419.         while ( VBReadLine( HeaderLine, SubjectsFile, WITHOUT_NEWLINE ) )
  420.         {
  421.         VBAppendVBReference( ArrayOfHeaderLines, HeaderLine );
  422.  
  423.         /*
  424.          * The HeaderLine which we just appended to the array will be
  425.          * destroyed in ASAKClose().
  426.          */
  427.         HeaderLine = VBCreate();
  428.         }
  429.  
  430.         VBDestroy( HeaderLine );
  431.     }
  432.  
  433.     VBDestroy( FullPathSubjectsFile );
  434.     SIODestroy( SubjectsFile );
  435. }
  436.  
  437.  
  438. void ASAKClose( void )
  439. {
  440.     int i;
  441.     VarBuf * p;
  442.  
  443.     assert( Open );
  444.  
  445.     if ( KilledFile != NULL )
  446.     {
  447.         SIODestroy( KilledFile );
  448.     KilledFile = NULL;
  449.  
  450.     VBDestroy( FullPathKilledFile );
  451.     FullPathKilledFile = NULL;
  452.     }
  453.  
  454.     /*
  455.      * We're about to write the new subjects to the Subjects file. To preserve
  456.      * pseudothreading, we must read in the old subjects before sorting. 
  457.      */
  458.     ReadOldSubjects( ArrayOfHeaderLines, CurrentNewsgroup );
  459.  
  460.     VBSortArrayOfVB( ArrayOfHeaderLines, PseudoThread );
  461.  
  462.     p = VBAddress( ArrayOfHeaderLines );
  463.     for ( i = 0; i < VBSize(ArrayOfHeaderLines) / sizeof(VarBuf); i++ )
  464.     {
  465.     if ( SubjectsFile == NULL )
  466.     {
  467.         assert( FullPathSubjectsFile == NULL );
  468.  
  469.         SubjectsFile = SIOCreate();
  470.         FullPathSubjectsFile = VBCreate();
  471.  
  472.         VBPrintf( FullPathSubjectsFile, "%s/%s/%V",
  473.               SkimDirectory(), "Subjects", CurrentNewsgroup );
  474.  
  475.         SIOFileOpenVB( SubjectsFile, FullPathSubjectsFile,
  476.                        OpenModeWriteDiscardOld );
  477.     }
  478.     assert( FullPathSubjectsFile != NULL );
  479.  
  480.     SIOPrintf( SubjectsFile, "%V\n", p[i] );
  481.     VBDestroy( p[i] );
  482.     }
  483.     VBDestroy( ArrayOfHeaderLines );
  484.  
  485.     SIODestroy( SubjectsFile );
  486.  
  487.     VBDestroy( FullPathSubjectsFile );
  488.     FullPathSubjectsFile = NULL;
  489.  
  490.     if ( HighestArticleNumber > 0 )
  491.     {
  492.     SetCurrentArticleNumber( VBAsString(CurrentNewsgroup),
  493.                              HighestArticleNumber + 1 );
  494.     }
  495.  
  496.     DestroyPattern( AutoSelectSubject );
  497.     AutoSelectSubject = NULL;
  498.  
  499.     DestroyPattern( KillSubject );
  500.     KillSubject = NULL;
  501.  
  502.     DestroyPattern( AutoSelectAuthor );
  503.     AutoSelectAuthor = NULL;
  504.  
  505.     DestroyPattern( KillAuthor );
  506.     KillAuthor = NULL;
  507.  
  508.     VBDestroy( CurrentNewsgroup );
  509.  
  510.     Open = False;
  511. }
  512.  
  513.  
  514. void ASAKExecute( VarBuf HeaderLine )
  515. {
  516.     VarBuf Subject = VBCreate();
  517.     VarBuf Author = VBCreate();
  518.     VarBuf ArticleNumberInVB = VBCreate();
  519.     static Boolean Initialized = False;
  520.     ArticleNumber CurrentArticleNumber;
  521.     char * SkimAutoSubject = NULL;
  522.     char * SkimAutoAuthor = NULL;
  523.  
  524.     assert( Open );
  525.  
  526.     if ( !Initialized )
  527.     {
  528.     /*
  529.      * Automatic selection and killing of subjects must be enabled by the 
  530.      * user.
  531.      */
  532.     if ( ( SkimAutoSubject = getenv("SKIMAUTOSUBJECT") ) != NULL )
  533.     {
  534.         if ( !strcmp( SkimAutoSubject, "CaseSensitive" ) )
  535.         {
  536.         SubjectCaseSensitive = True;
  537.         }
  538.         else if ( !strcmp( SkimAutoSubject, "CaseInsensitive" ) )
  539.         {
  540.         SubjectCaseSensitive = False;
  541.         }
  542.         else
  543.         {
  544.         SIOPrintf( StandardError, 
  545.                            "Invalid value in $SKIMAUTOSUBJECT: %s\n",
  546.                    SkimAutoSubject );
  547.         exit( EXIT_FAILURE );
  548.         }
  549.  
  550.         assert( AutoSelectSubject == NULL );
  551.         AutoSelectSubject = CompilePatternInFile(
  552.                     "Patterns/Subject/AutoSelect",
  553.                     VBAsString( CurrentNewsgroup ),
  554.                     SubjectCaseSensitive );
  555.  
  556.         assert( KillSubject == NULL );
  557.         KillSubject = CompilePatternInFile( 
  558.                     "Patterns/Subject/Kill",
  559.                     VBAsString( CurrentNewsgroup ),
  560.                     SubjectCaseSensitive );
  561.     }
  562.  
  563.     /*
  564.      * Automatic selection and killing of authors must be enabled by the 
  565.      * user. 
  566.      */
  567.     if ( ( SkimAutoAuthor = getenv("SKIMAUTOAUTHOR") ) != NULL )
  568.     {
  569.         if ( !strcmp( SkimAutoAuthor, "CaseSensitive" ) )
  570.         {
  571.         AuthorCaseSensitive = True;
  572.         }
  573.         else if ( !strcmp( SkimAutoAuthor, "CaseInsensitive" ) )
  574.         {
  575.         AuthorCaseSensitive = False;
  576.         }
  577.         else
  578.         {
  579.         SIOPrintf( StandardError, 
  580.                            "Invalid value in $SKIMAUTOAUTHOR: %s\n",
  581.                        SkimAutoAuthor );
  582.         exit( EXIT_FAILURE );
  583.         }
  584.  
  585.         assert( AutoSelectAuthor == NULL );
  586.         AutoSelectAuthor = CompilePatternInFile( 
  587.                    "Patterns/Author/AutoSelect",
  588.                    VBAsString( CurrentNewsgroup ),
  589.                    AuthorCaseSensitive );
  590.  
  591.         assert( KillAuthor == NULL );
  592.         KillAuthor = CompilePatternInFile( 
  593.                    "Patterns/Author/Kill",
  594.                    VBAsString( CurrentNewsgroup ),
  595.                    AuthorCaseSensitive );
  596.     }
  597.  
  598.     Initialized = True;
  599.     }
  600.  
  601.     if ( AutoSelectSubject != NULL || KillSubject != NULL )
  602.     {
  603.     GetSubjectFromHeaderLine( HeaderLine, Subject );
  604.     }
  605.  
  606.     if ( AutoSelectAuthor != NULL || KillAuthor != NULL )
  607.     {
  608.     GetAuthorFromHeaderLine( HeaderLine, Author );
  609.     }
  610.  
  611.     if ( AutoSelectSubject != NULL &&
  612.          !regexec( AutoSelectSubject, VBAsString(Subject), 0, NULL, 0 ) )
  613.     {
  614.     AutoSelectHeaderLine( VBAsString(CurrentNewsgroup), HeaderLine );
  615.     }
  616.     else if ( AutoSelectAuthor != NULL &&
  617.               !regexec( AutoSelectAuthor, VBAsString(Author), 0, NULL, 0 ) )
  618.     {
  619.     AutoSelectHeaderLine( VBAsString(CurrentNewsgroup), HeaderLine );
  620.     }
  621.     else if ( KillSubject != NULL &&
  622.               !regexec( KillSubject, VBAsString(Subject), 0, NULL, 0 ) )
  623.     {
  624.     KillHeaderLine( VBAsString(CurrentNewsgroup), HeaderLine );
  625.     }
  626.     else if ( KillAuthor != NULL &&
  627.               !regexec( KillAuthor, VBAsString(Author), 0, NULL, 0 ) )
  628.     {
  629.     KillHeaderLine( VBAsString(CurrentNewsgroup), HeaderLine );
  630.     }
  631.     else
  632.     {
  633.     NormalHeaderLine( VBAsString(CurrentNewsgroup), HeaderLine );
  634.     }
  635.  
  636.     GetArticleNumberFromHeaderLine( HeaderLine, ArticleNumberInVB );
  637.     if ( sscanf( VBAsString(ArticleNumberInVB), "%lu",
  638.          &CurrentArticleNumber ) != EOF )
  639.     {
  640.     if ( CurrentArticleNumber > HighestArticleNumber )
  641.     {
  642.         HighestArticleNumber = CurrentArticleNumber;
  643.     }
  644.     }
  645.     else
  646.     {
  647.     SIOPrintf( StandardError, "Format error in header line: %V.\n",
  648.                HeaderLine );
  649.     exit( EXIT_FAILURE );
  650.     }
  651.  
  652.     VBDestroy( Subject );
  653.     VBDestroy( Author );
  654.     VBDestroy( ArticleNumberInVB );
  655. }
  656.